using System;
using Sce.Pss.Core;

namespace UTFramework
{
	public class Camera
	{
        private Vector3 _mPos;   //Вектор позиции камеры
        private Vector3 _mView;  //Куда смотрит камера
        private Vector3 _mUp;    //Вектор верхнего направления
        private Vector3 _mStrafe;//Вектор для стрейфа (движения влево и вправо) камеры.
		private float _angleRot;
		private float _zoomFactor = 0;
		private float _upDownPos = 0;
		private float _angleY = 0;
		
        private Vector3 Cross(Vector3 vV1, Vector3 vV2, Vector3 vVector2)
        {
			Vector3 vNormal;
            Vector3 vVector1;
			vVector1 = vV1-vV2;

            // Если у нас есть 2 вектора (вектор взгляда и вертикальный вектор), 
            // у нас есть плоскость, от которой мы можем вычислить угол в 90 градусов.
            // Рассчет cross'a прост, но его сложно запомнить с первого раза. 
            // Значение X для вектора = (V1.Y * V2.Z) - (V1.Z * V2.Y)
            vNormal.X = ((vVector1.Y * vVector2.Z) - (vVector1.Z * vVector2.Y));

            // Значение Y = (V1.Z * V2.X) - (V1.X * V2.Z)
            vNormal.Y = ((vVector1.Z * vVector2.X) - (vVector1.X * vVector2.Z));

            // Значение Z = (V1.X * V2.Y) - (V1.Y * V2.X)
            vNormal.Z = ((vVector1.X * vVector2.Y) - (vVector1.Y * vVector2.X));

            // *ВАЖНО* Вы не можете менять этот порядок, иначе ничего не будет работать.
            // Должно быть именно так, как здесь. Просто запомните, если вы ищите Х, вы не
            // используете значение X двух векторов, и то же самое для Y и Z. Заметьте,
            // вы рассчитываете значение из двух других осей, и никогда из той же самой.

            // Итак, зачем всё это? Нам нужно найти ось, вокруг которой вращаться. Вращение камеры
            // влево и вправо простое - вертикальная ось всегда (0,1,0). 
            // Вращение камеры вверх и вниз отличается, так как оно происходит вне 
            // глобальных осей. Достаньте себе книгу по линейной алгебре, если у вас 
            // её ещё нет, она вам пригодится.

            // вернем результат.
            return vNormal;
        }

        private float Magnitude(Vector3 vNormal)
        {
            // Это даст нам величину нашей нормали, 
            // т.е. длину вектора. Мы используем эту информацию для нормализации
            // вектора. Вот формула: magnitude = sqrt(V.X^2 + V.Y^2 + V.Z^2)   где V - вектор.

            return (float)Math.Sqrt((vNormal.X * vNormal.X) +
                    (vNormal.Y * vNormal.Y) +
                    (vNormal.Z * vNormal.Z));
        }

        private Vector3 Normalize(Vector3 vVector)
        {
            // Вы спросите, для чего эта ф-я? Мы должны убедиться, что наш вектор нормализирован.
            // Вектор нормализирован - значит, его длинна равна 1. Например,
            // вектор (2,0,0) после нормализации будет (1,0,0).

            // Вычислим величину нормали
            float magnitude = Magnitude(vVector);

            // Теперь у нас есть величина, и мы можем разделить наш вектор на его величину.
            // Это сделает длинну вектора равной единице, так с ним будет легче работать.
      			
			vVector = vVector/magnitude;
			
            return vVector;
        }

        public void Position_Camera(Vector3 pos, Vector3 view, Vector3 up)
        {
			_mPos = pos;
			_mView = view;
			_mUp = up;

        }
		
		public void zoom_View(float zValue)
		{
			_zoomFactor += zValue;
		}
		
        public void Rotate_View(float speed)
        {
            Vector3 vVector;// Полчим вектор взгляда
			
			vVector = _mView - _mView;

            _mView.Z = (float)(_mPos.Z + Math.Sin(speed) * vVector.X + Math.Cos(speed) * vVector.Z);
            _mView.X = (float)(_mPos.X + Math.Cos(speed) * vVector.X - Math.Sin(speed) * vVector.Z);
        }

        public void Rotate_Position(float angle, float x, float y, float z)
        {
			_mPos = _mPos-_mView;
			
            Vector3 vVector = _mPos;
            Vector3 AVector;

            float SinA = (float)Math.Sin(Math.PI * angle / 180.0);
            float CosA = (float)Math.Cos(Math.PI * angle / 180.0);

            // Найдем новую позицию X для вращаемой точки 
            AVector.X = (CosA + (1 - CosA) * x * x) * vVector.X;
            AVector.X += ((1 - CosA) * x * y - z * SinA) * vVector.Y;
            AVector.X += ((1 - CosA) * x * z + y * SinA) * vVector.Z;

            // Найдем позицию Y 
            AVector.Y = 	((1 - CosA) * x * y + z * SinA) 	* vVector.X;
            AVector.Y += 	(CosA + (1 - CosA) * y * y) 		* vVector.Y;
            AVector.Y += 	((1 - CosA) * y * z - x * SinA) 	* vVector.Z;

            // И позицию Z 
            AVector.Z = ((1 - CosA) * x * z - y * SinA) * vVector.X;
            AVector.Z += ((1 - CosA) * y * z + x * SinA) * vVector.Y;
            AVector.Z += (CosA + (1 - CosA) * z * z) * vVector.Z;
			
			_mPos = _mView + AVector;
			_angleY += angle;
        }
		
		public void Rotate_PositionX(float angle)
        {
			if (angle>0)
			{
				if (_angleRot<90)
				{
					_angleRot += angle;
				}
			}
			else
			{
				if (_angleRot>-90)
				{
					_angleRot += angle;
				}
			}
			
            _upDownPos = 15*(float)Math.Cos((_angleRot+90)*Math.PI/180);// _mView.Y + AVector.Y;
        }
		
        public void Move_Camera(float speed) //Задаем скорость
        {
            Vector3 vVector; //Получаем вектор взгляда
			vVector = _mView - _mPos;

            vVector.Y = 0.0f; // Это запрещает камере подниматься вверх
            vVector = Normalize(vVector);

            _mPos.X += vVector.X * speed;
            _mPos.Z += vVector.Z * speed;
            _mView.X += vVector.X * speed;
            _mView.Z += vVector.Z * speed;
        }
		
        public void Strafe(float speed)
        {
            // добавим вектор стрейфа к позиции
            _mPos.X += _mStrafe.X * speed;
            _mPos.Z += _mStrafe.Z * speed;

            // Добавим теперь к взгляду
            _mView.X += _mStrafe.X * speed;
            _mView.Z += _mStrafe.Z * speed;
        }

        public void update()
        {
            Vector3 vCross = Cross(_mView, _mPos, _mUp);

            //Нормализуем вектор стрейфа
            _mStrafe = Normalize(vCross);
        }

        public void upDown(float speed)
        {
            _mPos.Y += speed;
			_mView.Y += speed;
        }

		public Matrix4 LookAt ()
		{
			return Matrix4.LookAt( this.getPosition(), this.getView(), this.getUp() );
		}

		public Vector3 getPosition() //Возвращает вектор позиции камеры
		{
			Vector3 retVectPos;
			retVectPos = _mPos;
			retVectPos.Y+=_upDownPos;
			
			return retVectPos;
		}
		
		public Vector3 getView() //Возвращает вектор Вида камеры
		{
			return _mView;
		}
		
		public Vector3 getUp() //Возвращает вектор Верха камеры
		{
			return _mUp;
		}
		
		public float getPosX() //Возвращает позицию камеры по Х
        {
            return _mPos.X;
        }

        public float getPosY() //Возвращает позицию камеры по Y
        {
            return _mPos.Y + _upDownPos;
        }

        public float getPosZ() //Возвращает позицию камеры по Z
        {
            return _mPos.Z;
        }

        public float getViewX() //Возвращает позицию взгляда по Х
        {
            return _mView.X;
        }

        public float getViewY() //Возвращает позицию взгляда по Y
        {
            return _mView.Y;
        }

        public float getViewZ() //Возвращает позицию взгляда по Z
        {
            return _mView.Z;
        }
		
		public float getUpX() //Возвращает позицию взгляда по Х
        {
            return _mUp.X;
        }

        public float getUpY() //Возвращает позицию взгляда по Y
        {
            return _mUp.Y;
        }

        public float getUpZ() //Возвращает позицию взгляда по Z
        {
            return _mUp.Z;
        }
		
		public float getZoom()
		{
			return _zoomFactor;
		}
		
		public float getAngleY()
		{
			return _angleY;	
		}
    }
}

